package com.nowcoder.topic.stack.middle;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;

/**
 * NC386 子数组的最小值之和
 * @author d3y1
 */
public class NC386 {
    private final int MOD = 1000000007;

    /**
     * 代码中的类名、方法名、参数名已经指定，请勿修改，直接返回方法规定的值即可
     *
     *
     * @param nums int整型一维数组
     * @return int整型
     */
    public int sumSubarr (ArrayList<Integer> nums) {
        int[] iNums = nums.stream().mapToInt(Integer::intValue).toArray();
        // int[] iNums = nums.stream().mapToInt(i->i).toArray();
        return solution1(iNums);
        // return solution2(iNums);
    }

    /**
     * 动态规划 + 单调栈
     *
     * 举例子找规律:
     * nums数组
     * 0  1  2  3  4  5  6  7  8  9  10
     * 23 43 18 16 35 23 6  11 6  33 30
     *
     * leftMap(i)  表示当前元素nums[i]左边第一个小于当前元素的元素索引 -1:表示没找到(当前元素左边都比当前元素大)
     * rightMap(i) 表示当前元素nums[i]右边第一个小于当前元素的元素索引  N:表示没找到(当前元素右边都比当前元素大)
     *      i       0  1  2  3  4  5  6  7  8  9  10
     * leftMap(i)  -1  0 -1 -1  3  3 -1  6 -1  8  8
     * rightMap(i)  2  2  3  6  5  6  11 8  11 10 11
     *
     * dp[i]表示以元素nums[i]为开头的所有连续子数组的最小值的和
     *    i    0  1  2  3  4  5  6  7  8  9  10
     * dp[0]   23 23 18 16 16 16 6  6  6  6  6
     * dp[1]      43 18 16 16 16 6  6  6  6  6
     * dp[2]         18 16 16 16 6  6  6  6  6
     * dp[3]            16 16 16 6  6  6  6  6
     * dp[4]               35 23 6  6  6  6  6
     * dp[5]                  23 6  6  6  6  6
     * dp[6]                     6  6  6  6  6
     * dp[7]                        11 6  6  6
     * dp[8]                           6  6  6
     * dp[9]                              33 30
     * dp[10]                                30
     *
     * dp[i] = nums[i]*(rightMap.get(i)-i)+dp[rightMap.get(i)]
     *
     * @param nums
     * @return
     */
    private int solution1(int[] nums){
        int N = nums.length;

        Map<Integer, Integer> rightMap = new HashMap<>();
        Stack<Integer> rightStack = new Stack<>();
        int num;
        // 右边 降序
        for (int i=N-1; i>=0; i--){
            // 找到当前元素右边第一个小于当前元素的元素索引 N:表示没找到, 当前元素右边都比当前元素大
            num = nums[i];
            // 单调增
            while (!rightStack.isEmpty() && num<=nums[rightStack.peek()]){
                rightStack.pop();
            }
            rightMap.put(i, rightStack.isEmpty() ? N : rightStack.peek());
            rightStack.push(i);
        }

        int sum = 0;
        int[] dp = new int[N+1];
        for(int i=N-1; i>=0; i--){
            dp[i] = nums[i]*(rightMap.get(i)-i)+dp[rightMap.get(i)];
            sum = (sum+dp[i]) % MOD;
        }

        return sum;
    }

    /**
     * 动态规划 + 单调栈
     *
     * 举例子找规律:
     * nums数组
     * 0  1  2  3  4  5  6  7  8  9  10
     * 23 43 18 16 35 23 6  11 6  33 30
     *
     * leftMap(i)  表示当前元素nums[i]左边第一个小于当前元素的元素索引 -1:表示没找到(当前元素左边都比当前元素大)
     * rightMap(i) 表示当前元素nums[i]右边第一个小于当前元素的元素索引  N:表示没找到(当前元素右边都比当前元素大)
     *      i       0  1  2  3  4  5  6  7  8  9  10
     * leftMap(i)  -1  0 -1 -1  3  3 -1  6 -1  8  8
     * rightMap(i)  2  2  3  6  5  6  11 8  11 10 11
     *
     * dp[i]表示以元素nums[i]为结尾的所有连续子数组的最小值的和
     *    i    0  1  2  3  4  5  6  7  8  9  10
     * dp[0]   23
     * dp[1]   23 43
     * dp[2]   18 18 18
     * dp[3]   16 16 16 16
     * dp[4]   16 16 16 16 35
     * dp[5]   16 16 16 16 23 23
     * dp[6]   6  6  6  6  6  6  6
     * dp[7]   6  6  6  6  6  6  6  11
     * dp[8]   6  6  6  6  6  6  6   6  6
     * dp[9]   6  6  6  6  6  6  6   6  6 33
     * dp[10]  6  6  6  6  6  6  6   6  6 30 30
     *
     *         { nums[i]*(i-leftMap.get(i))                       , leftMap.get(i) == -1
     * dp[i] = {
     *         { nums[i]*(i-leftMap.get(i)) + dp[leftMap.get(i)]  , leftMap.get(i) != -1
     *
     * @param nums
     * @return
     */
    private int solution2(int[] nums){
        int N = nums.length;

        Map<Integer, Integer> leftMap = new HashMap<Integer, Integer>();
        Stack<Integer> leftStack = new Stack<>();
        int num;
        // 左边 升序
        for (int i=0; i<N; i++){
            // 找到当前元素左边第一个小于当前元素的元素索引 -1:表示没找到, 当前元素左边都比当前元素大
            num = nums[i];
            // 单调增
            while (!leftStack.isEmpty() && num<=nums[leftStack.peek()]){
                leftStack.pop();
            }
            leftMap.put(i, leftStack.isEmpty() ? -1 : leftStack.peek());
            leftStack.push(i);
        }

        int sum = 0;
        int[] dp = new int[N];
        for(int i=0; i<N; i++){
            dp[i] = nums[i]*(i-leftMap.get(i));
            if(leftMap.get(i) != -1){
                dp[i] += dp[leftMap.get(i)];
            }
            sum = (sum+dp[i]) % MOD;
        }

        return sum;
    }
}